package service

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"github.com/cihub/seelog"
	"shop/config"
	"shop/config/constant"
	"shop/dao"
	"shop/entity"
	"shop/utils/convertutil"
	"shop/utils/genutil"
	"shop/utils/httputil"
	"shop/utils/lockutil"
	"shop/utils/result"
	"strconv"
	"strings"
	"time"
)

func GetUserOrderList(userId int64, pageNo, pageSize int) *result.Result {
	list := make([]*entity.OrderInfo, 0, 0)
	db := dao.GetDB().Table(entity.TableNameOrderInfo).
		Where("user_id", userId).Order("create_time DESC").Offset((pageNo - 1) * pageSize).Limit(pageSize).
		Find(&list)
	if db.Error != nil {
		seelog.Errorf("fail to getUserOrderList, err: %v", db.Error)
	}
	for _, order := range list {
		order.ParseGoodsList() // 反序列化订单商品列表
	}
	return result.Ok(list)
}

func GetOrderDetail(orderNo string) *result.Result {
	order := entity.OrderInfo{}
	db := dao.GetDB().Model(&order).Where("order_no", orderNo).Take(&order)
	if order.OrderNo == "" && db.Error != nil {
		seelog.Warnf("order not exists, err:%v", db.Error)
		return result.Fail("order not exists")
	}
	order.ParseGoodsList() // 反序列化订单商品列表
	return result.Ok(order)
}

// GetOrderStatus 查询订单状态, 扫码支付时轮询用
func GetOrderStatus(orderNo string) *result.Result {
	status := 0
	err := dao.GetDB().Table(entity.TableNameOrderInfo).Select("order_status").Where("order_no", orderNo).Take(&status).Error
	if err != nil {
		seelog.Errorf("fail to search order_status from db, err: %v", err)
	}
	return result.Ok(status)
}

// 创建订单时的锁最大时间
const createOrderLockTime = 15 // second

func CreateOrder(params *entity.OrderInfo) *result.Result {
	// check shopStatus
	shop := GetShopInfo(params.ShopId)
	if shop.Status != constant.ShopStatusNormal {
		return result.Fail("店铺状态：" + constant.ShopStatusCN(shop.Status))
	}

	// check parameters
	if len(params.GoodsList) == 0 {
		return result.Fail("商品列表为空")
	}
	if params.UserId == 0 || params.ShopId == 0 {
		return result.Fail("用户id或商家id不能为0")
	}
	if params.TakeType != constant.TakeTypeTakeOut && params.TakeType != constant.TakeTypeOfflineTakeIn {
		return result.Fail("非法取餐方式，只能是1和2")
	}
	params.UserPhone = strings.TrimSpace(params.UserPhone)
	params.AddressDetail = strings.TrimSpace(params.AddressDetail)
	params.Receiver = strings.TrimSpace(params.Receiver)
	if params.AddressDetail == "" || params.UserPhone == "" || params.Receiver == "" {
		return result.Fail("收货地址，联系电话或收货人都不能为空")
	}

	// 校验订单商品列表
	params.TotalPrice = 0
	for i := range params.GoodsList {
		orderGoods := params.GoodsList[i]
		goodsInDB := GetGoodsById(orderGoods.GoodsId)
		if goodsInDB == nil {
			return result.Fail(orderGoods.Name + "商品不存在")
		}

		err := validPropertyList(orderGoods, goodsInDB)
		if err != "" {
			seelog.Errorf("订单商品校验失败，err: %s", err)
			return result.Fail(err)
		}
		params.TotalPrice += int64(orderGoods.Price * orderGoods.Count)
	}
	if params.TakeType == constant.TakeTypeTakeOut {
		if params.TotalPrice < int64(shop.SendingNeedPrice) {
			return result.Fail(fmt.Sprintf("不得低于起送价：%d", shop.SendingNeedPrice))
		}
		params.TotalPrice += int64(shop.SendingPrice)
		params.TotalPrice += int64(shop.PackingPrice)
	}
	params.OrderStatus = constant.OrderStatusNotPay
	params.CreateTime = time.Now().UnixNano() / 1000_000
	params.OrderNo = genutil.GenOrderNo(params.CreateTime, params.UserId) // gen orderNo
	params.GoodsListToStr()

	// 开始下单事务，锁用户id
	key := constant.CacheKeyPrefixLockCreateOrder + strconv.FormatInt(params.UserId, 10)
	defer lockutil.ReleaseLock(key, params.OrderNo)
	if !lockutil.TryLock(key, params.OrderNo, createOrderLockTime) {
		return result.Fail("存在未创建完的订单")
	}
	db := dao.GetTx().Model(&params).Create(&params).Commit()
	if db.Error != nil || db.RowsAffected != 1 {
		seelog.Errorf("新增订单失败, err:%v", db.Error)
		return result.Fail("下单失败，数据插入异常")
	}
	seelog.Tracef("订单创建成功, order: %v", params)
	return result.Ok(params.OrderNo)
}

// validPropertyList 校验-订单商品里选择的属性
func validPropertyList(orderGoods *entity.OrderGoods, goodsInDB *entity.Goods) string {
	for _, propertyInDB := range goodsInDB.PropertyList {
		selectedOption := orderGoods.PropertyMap[propertyInDB.Name]
		if (propertyInDB.IsMain || propertyInDB.Required) && selectedOption == "" { // 必选属性
			return "缺少必选属性: " + propertyInDB.Name
		}

		var optionInDB *entity.GoodsPropertyOption
		for _, option := range propertyInDB.Options {
			if selectedOption == option.Name {
				optionInDB = option
				break
			}
		}
		if optionInDB == nil {
			return "该选项不存在：" + propertyInDB.Name + "-" + selectedOption
		}
		if optionInDB.Disabled {
			return "该选项已经下架: " + propertyInDB.Name + "-" + optionInDB.Name
		}
		if propertyInDB.IsMain && (optionInDB.Count == 0 || orderGoods.Count > optionInDB.Count) {
			return "该规格商品库存不够：" + propertyInDB.Name + "-" + optionInDB.Name
		}

		if propertyInDB.IsMain {
			orderGoods.Price = optionInDB.Price
		}
	}
	return ""
}

// ConfirmFinishPay 初步确认支付
func ConfirmFinishPay(orderNo string, userId int64) *result.Result {
	tx := dao.GetTx().Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=? and order_status=?", orderNo, userId, constant.OrderStatusNotPay).
		UpdateColumn("order_status", constant.OrderStatusWaitSend)
	return result.Ok(tx.Commit().RowsAffected)
}

// CancelOrder 取消并删除订单
func CancelOrder(orderNo string, userId int64) *result.Result {
	rows := dao.GetTx().Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=? and order_status=?", orderNo, userId, constant.OrderStatusNotPay).
		Delete(nil).Commit().RowsAffected
	return result.Ok(rows == 1)
}

func ConfirmReceive(orderNo string, userId int64) *result.Result {
	rows := dao.GetTx().Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=? and order_status=?", orderNo, userId, constant.OrderStatusWaitTake).
		Updates(&entity.OrderInfoFilter{
			OrderStatus: convertutil.Int8Ptr(constant.OrderStatusFinished),
			FinishTime:  convertutil.Int64Ptr(time.Now().UnixNano() / 1000_000),
		}).Commit().RowsAffected
	return result.Ok(rows)
}

type XinyeyunPrintOrderParams struct {
	Sn        string `json:"sn"`      // 打印机编号
	Content   string `json:"content"` // 不能超过 4096 字节
	Copies    int    `json:"copies"`  // 打印多少份
	User      string `json:"user"`
	Timestamp string `json:"timestamp"`
	Sign      string `json:"sign"`
}

type XinyeyunPrintOrderResponse struct {
	Msg                string `json:"msg"`
	Code               int    `json:"code"`
	Data               string `json:"data"`
	ServerExecutedTime int    `json:"serverExecutedTime"`
}

// PrinterOrder 打印订单信息小票（打印机）
func PrinterOrder(orderNo string) *result.Result {
	resOrder := GetOrderDetail(orderNo)
	if resOrder.Code != result.ErrCodeOk {
		return resOrder
	}
	order := resOrder.Data.(entity.OrderInfo)
	shop := GetShopById(order.ShopId)
	if shop == nil || shop.Id == 0 {
		return result.Fail("店铺不存在")
	}

	params := XinyeyunPrintOrderParams{
		Sn:        shop.PrinterId,
		Copies:    1,
		User:      config.Conf.XinyeyunUsername,
		Timestamp: strconv.FormatInt(time.Now().Unix(), 10),
	}
	signByte := sha1.Sum([]byte(config.Conf.XinyeyunUsername + config.Conf.XinyeyunApiKey + params.Timestamp))
	params.Sign = hex.EncodeToString(signByte[:])
	// 生成订单信息
	goodsListStr := ""
	for _, goods := range order.GoodsList {
		goodsListStr += fmt.Sprintf("%s    ￥%d x%d \n", goods.Name, goods.Price/100, goods.Count)
	}
	if order.TakeType == constant.TakeTypeTakeOut {
		goodsListStr += fmt.Sprintf("打包费    ￥%d \n", shop.PackingPrice/100)
		goodsListStr += fmt.Sprintf("配送费    ￥%d \n", shop.SendingPrice/100)
	}
	params.Content = fmt.Sprintf(`
<C><IMG160></IMG>
<CB>**%s**
--------------------------------
下单时间: %s
系统订单号: %s
微信交易号：%s
*************商品列表************
%s
--------------------------------
<B>订单总价：￥%d
<B>实际支付：￥%d
<BOLD>地址：%s
<BOLD>收货人：%s
订单备注：%s
`, shop.Name,
		convertutil.FormatDateChinaDate(time.Unix(order.CreateTime/1000, 0)),
		order.OrderNo,
		order.WxPayTransactionId,
		goodsListStr,
		order.TotalPrice/100,
		order.PayPrice/100,
		order.AddressDetail,
		order.Receiver+", "+order.UserPhone,
		order.ExtraInfo,
	)

	resp := &XinyeyunPrintOrderResponse{}
	convertutil.ParseJson(httputil.PostJson("https://platform.xpyun.net/api/openapi/xprinter/print", params), resp)
	if resp.Code != 0 {
		seelog.Errorf("打印订单失败, %s", resp.Msg)
		return result.Fail(resp.Msg)
	}
	return result.Ok(resp.Data)
}
